package com.wimcorp.magic.behaviour;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import javax.persistence.CascadeType;
import javax.persistence.Embeddable;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OrderColumn;
import javax.persistence.Transient;

import com.wimcorp.magic.abilities.Ability;
import com.wimcorp.magic.behaviour.attack.AttackBehaviour;
import com.wimcorp.magic.behaviour.attack.DeathTouch;
import com.wimcorp.magic.behaviour.attack.DefaultAttack;
import com.wimcorp.magic.behaviour.attack.DoubleStrike;
import com.wimcorp.magic.behaviour.attack.FirstStrike;
import com.wimcorp.magic.behaviour.attack.LifeLink;
import com.wimcorp.magic.behaviour.attack.NotFirstStrike;
import com.wimcorp.magic.behaviour.attack.Trample;
import com.wimcorp.magic.behaviour.counter.CounterBehavior;
import com.wimcorp.magic.cards.Creature;
import com.wimcorp.magic.cards.PlayingCard;
import com.wimcorp.magic.combat.Target;
import com.wimcorp.magic.players.Player;

@Embeddable
public class BehaviourHolder extends Behaviour {

	@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
	@JoinTable(name = "playingcard_behaviour", joinColumns = @JoinColumn(name = "playingcard_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "behaviour_id", referencedColumnName = "id"))
	@OrderColumn
	private Set<Behaviour> behaviours = new HashSet<Behaviour>();

	@Transient
	private List<Behaviour> duplicateBehaviors = new ArrayList<Behaviour>();

	@Transient
	private PlayingCard playingCard;

	@Transient
	private static Set<Ability> creatureDamageObservers = new HashSet<Ability>();

	@Transient
	private static Set<Ability> playerDamageObservers = new HashSet<Ability>();

	protected BehaviourHolder() {
	}

	public BehaviourHolder(PlayingCard playingCard) {
		this.playingCard = playingCard;
	}

	public void addBehaviour(Behaviour behaviour) {
		if (!behaviours.contains(behaviour)) {
			behaviours.add(behaviour);
			if(behaviour instanceof CounterBehavior && playingCard instanceof Creature){
				Creature creature = (Creature)playingCard;
				CounterBehavior counter = (CounterBehavior) behaviour;
				creature.setActualPower(creature.getActualPower() + counter.getPower());
				creature.setActualToughness(creature.getActualToughness() + counter.getToughness());
			}
		} else {
			duplicateBehaviors.add(behaviour);
		}
	}

	public void removeBehaviour(Behaviour behaviour) {
		if (duplicateBehaviors.contains(behaviour)) {
			duplicateBehaviors.remove(behaviour);
			if(behaviour instanceof CounterBehavior && playingCard instanceof Creature){
				Creature creature = (Creature)playingCard;
				CounterBehavior counter = (CounterBehavior) behaviour;
				creature.setActualPower(creature.getActualPower() - counter.getPower());
				creature.setActualToughness(creature.getActualToughness() - counter.getToughness());
			}
		} else {
			behaviours.remove(behaviour);
		}
	}

	public Set<Behaviour> getBehaviours() {
		return behaviours;
	}

	public boolean hasBehaviour(Behaviour behaviour) {
		return behaviours.contains(behaviour);
	}

	public void attack(Target target) {
		Creature attacker = (Creature) playingCard;
		if (target instanceof Creature) {
			for (Ability ability : creatureDamageObservers) {
				attacker.getPlayer().cast(ability);
			}
		} else if (target instanceof Player) {
			for (Ability ability : playerDamageObservers) {
				attacker.getPlayer().cast(ability);
			}
		}
		AttackBehaviour attackBehaviourWrapper = wrapAttackBehavior(new DefaultAttack(attacker));
		if (attacker.hasBehaviour(FirstStrike.instance())) {
			attackBehaviourWrapper = new FirstStrike(attacker,attackBehaviourWrapper);
		} else if (attacker.hasBehaviour(DoubleStrike.instance())) {
			attackBehaviourWrapper = new DoubleStrike(attacker,attackBehaviourWrapper);
		}else{
			attackBehaviourWrapper = new NotFirstStrike(attacker,attackBehaviourWrapper);
		}
		if (attackBehaviourWrapper != null) {
			attackBehaviourWrapper.attack(target);
		}
		
		
	}
	
	public void counter(Creature target) {
		Creature attacker = (Creature) playingCard;
		if (target instanceof Creature) {
			for (Ability ability : creatureDamageObservers) {
				attacker.getPlayer().cast(ability);
			}
		} 
		AttackBehaviour attackBehaviourWrapper = wrapAttackBehavior(new DefaultAttack(attacker));
		if (attacker.hasBehaviour(FirstStrike.instance())) {
			attackBehaviourWrapper = new FirstStrike(attacker,attackBehaviourWrapper);
		} else if (attacker.hasBehaviour(DoubleStrike.instance())) {
			attackBehaviourWrapper = new DoubleStrike(attacker,attackBehaviourWrapper);
		} 
		if (attackBehaviourWrapper != null) {
			attackBehaviourWrapper.counter(target);
		}
		
	}

	private AttackBehaviour wrapAttackBehavior(AttackBehaviour attack) {
		Set<AttackBehaviour> attackBehaviours = getAttackBehaviours();
		Creature creature = (Creature) playingCard;
		for (AttackBehaviour attackBehaviour : attackBehaviours) {
			if (attackBehaviour instanceof DeathTouch) {
				attack = new DeathTouch(creature, attack);
			} else if (attackBehaviour instanceof LifeLink) {
				attack = new LifeLink(creature, attack);
			} else if (attackBehaviour instanceof Trample) {
				attack = new Trample(creature, attack);
			} 
		}
		return attack;
	}

	private Set<AttackBehaviour> getAttackBehaviours() {
		Set<AttackBehaviour> attackBehaviours = new TreeSet<AttackBehaviour>(new Comparator<AttackBehaviour>() {
			public int compare(AttackBehaviour a, AttackBehaviour b) {
				return (new Integer(b.priority())).compareTo(a.priority());
			}
		});
		for (Behaviour behaviour : behaviours) {
			if (behaviour instanceof AttackBehaviour) {
				attackBehaviours.add((AttackBehaviour) behaviour);
			}
		}
		return attackBehaviours;

	}

	public Set<CounterBehavior> getCounters() {
		Set<CounterBehavior> counters = new TreeSet<CounterBehavior>(new Comparator<CounterBehavior>() {
			public int compare(CounterBehavior a, CounterBehavior b) {
				return (new Integer(b.getToughness())).compareTo(a.getToughness());
			}
		});
		for (Behaviour behaviour : behaviours) {
			if (behaviour instanceof CounterBehavior) {
				counters.add((CounterBehavior) behaviour);
			}
		}
		return counters;
	}

	public int size() {
		return behaviours.size();
	}

	public String toString() {
		StringBuilder behaviourSB = new StringBuilder();
		for (Behaviour behaviour : behaviours) {
			behaviourSB.append(behaviour);
			behaviourSB.append("\n");
		}
		return behaviourSB.toString();
	}

	public void registerCreatureDamageListener(Ability ability) {
		creatureDamageObservers.add(ability);
	}

	public void registerPlayerDamageListener(Ability ability) {
		playerDamageObservers.add(ability);
	}

	public void unRegisterCreatureDamageListener(Ability ability) {
		creatureDamageObservers.remove(ability);
	}

	public void unRegisterPlayerDamageListener(Ability ability) {
		playerDamageObservers.remove(ability);
	}

	

}
